﻿using System.Net;
using System.Reflection;
using Devonline.AspNetCore;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.Communication.Server;

/// <summary>
/// 针对客户端 TClient 传输类型 T 的通用基类
/// </summary>
/// <typeparam name="TClient"></typeparam>
/// <typeparam name="T"></typeparam>
public abstract class CommunicationServer<TClient, T> : Hub<TClient> where TClient : class, ICommunicatorClient<T> where T : class
{
    private const string LOG_INFO = "User {user} in client {client} will send data: ";
    private const string LOG_CONNECTION = "User {user} in client {client} {connectionState} with connectionId: {connectionId}";
    protected const string CACHE_ONLINE_USERS = "ONLINE_USERS";
    protected readonly ILogger<CommunicationServer<TClient, T>> _logger;
    protected readonly IDistributedCache _cache;
    protected readonly HttpContext _httpContext;
    protected readonly HostSetting _hostSetting;

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="cache">缓存</param>
    /// <param name="httpContextAccessor">http上下文</param>
    /// <param name="hostSetting">设置</param>
    public CommunicationServer(
        ILogger<CommunicationServer<TClient, T>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        HostSetting hostSetting
        )
    {
        if (httpContextAccessor.HttpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContextAccessor.HttpContext), "HttpContext can't be null!");
        }

        _httpContext = httpContextAccessor.HttpContext;
        _logger = logger;
        _cache = cache;
        _hostSetting = hostSetting;
    }

    #region 公开成员
    /// <summary>
    /// 客户端连接到服务器时
    /// </summary>
    /// <returns></returns>
    public override async Task OnConnectedAsync()
    {
        if (string.IsNullOrWhiteSpace(User))
        {
            _logger.LogError("There has no identity in user connection");
            return;
        }

        //记录当前客户端和客户端连接的对应关系
        _logger.LogWarning(LOG_CONNECTION, User, Client, "connected", Context.ConnectionId);
        await Groups.AddToGroupAsync(Context.ConnectionId, User);
        await OnlineAsync();
        await base.OnConnectedAsync();
    }
    /// <summary>
    /// 客户端从服务器断开时
    /// </summary>
    /// <param name="exception"></param>
    /// <returns></returns>
    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        if (!string.IsNullOrWhiteSpace(User))
        {
            //移除当前用户和客户端连接的对应关系
            _logger.LogWarning(exception, LOG_CONNECTION, User, Client, "disconnected", Context.ConnectionId);
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, User);
            await OfflineAsync();
        }

        await base.OnDisconnectedAsync(exception);
    }
    /// <summary>
    /// 执行指令并返回结果
    /// 所有指令均为无参内部成员方法, 返回值均为字符串
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public virtual string Command(string name)
    {
        var methodInfo = GetType().GetMethod(name, BindingFlags.NonPublic & BindingFlags.Instance);
        if (methodInfo != null)
        {
            return methodInfo.Invoke(this, null)?.ToString() ?? string.Empty;
        }

        return HttpStatusCode.NotFound.ToString();
    }
    #endregion

    #region 内部成员
    /// <summary>
    /// 连接中的默认用户
    /// 区分用户和客户端, 是因为, 一个用户可以同时登录多个客户端
    /// </summary>
    protected virtual string? User => _httpContext.GetContextOption<string>(_hostSetting.User) ?? _httpContext.GetUserName();
    /// <summary>
    /// 连接中的默认客户端
    /// 区分用户和客户端, 是因为, 一个用户可以同时登录多个客户端
    /// </summary>
    protected virtual string? Client => _httpContext.GetContextOption<string>(_hostSetting.Client) ?? Context.ConnectionId;
    /// <summary>
    /// 连接中的默认接收者, 接收者默认都是用户
    /// </summary>
    protected virtual string? Receiver => _httpContext.GetContextOption<string>(_hostSetting.Receiver);

    /// <summary>
    /// 转发消息到指定的接收者
    /// 若无接收者, 则使用默认接收者
    /// </summary>
    /// <typeparam name="T">传输的数据内容类型</typeparam>
    /// <param name="t">数据</param>
    /// <param name="receiver">接收者</param>
    /// <returns></returns>
    protected virtual async Task SendAsync(T t, string? receiver)
    {
        receiver ??= Receiver;
        if (string.IsNullOrWhiteSpace(receiver))
        {
            _logger.LogError("Receiver must not be null");
            return;
        }

        try
        {
            //发送消息到接收者
            _logger.LogInformation($"User {User} in client {Client} will send data to {receiver}, " + t.ToJsonString());
            await Clients.Group(receiver).Receive(t);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"User {User} in client {Client} send data to {receiver} throw exception, " + t.ToJsonString());
        }
    }
    /// <summary>
    /// 转发消息到指定的接收者
    /// 若无接收者, 则使用默认接收者
    /// </summary>
    /// <typeparam name="T">传输的数据内容类型</typeparam>
    /// <param name="data">数据</param>
    /// <param name="receiver">接收者</param>
    /// <returns></returns>
    protected virtual async Task SendAsync(IEnumerable<T> data, string? receiver)
    {
        receiver ??= Receiver;
        if (string.IsNullOrWhiteSpace(receiver))
        {
            _logger.LogError("Receiver must not be null");
            return;
        }

        try
        {
            //发送消息到接收者
            _logger.LogInformation($"User {User} in client {Client} will send data to {receiver}, " + data.ToJsonString());
            await Clients.Group(receiver).Receive(data);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"User {User} in client {Client} send data to {receiver} throw exception, " + data.ToJsonString());
        }
    }
    /// <summary>
    /// 用户上线刷新缓存
    /// </summary>
    /// <returns></returns>
    protected virtual async Task OnlineAsync()
    {
        if (User is null)
        {
            return;
        }

        //刷新用户服务器链接缓存
        var cacheKey = AppSettings.CACHE_USER + User;
        var models = await _cache.GetValueAsync<List<UserCacheModel>>(cacheKey) ?? new List<UserCacheModel>();
        models.Add(new UserCacheModel { Client = Client, ConnectionId = Context.ConnectionId, Server = _hostSetting.Server });
        await _cache.SetValueAsync(cacheKey, models);

        //刷新在线用户列表
        cacheKey = AppSettings.CACHE_APPLICATION + CACHE_ONLINE_USERS;
        var users = await _cache.GetValueAsync<List<string>>(cacheKey) ?? new List<string>();
        if (!users.Any(x => x == User))
        {
            users.Add(User);
            await _cache.SetValueAsync(cacheKey, users);
        }
    }
    /// <summary>
    /// 用户离线刷新缓存
    /// </summary>
    /// <returns></returns>
    protected virtual async Task OfflineAsync()
    {
        if (User is null)
        {
            return;
        }

        //刷新用户服务器链接缓存
        var cacheKey = AppSettings.CACHE_USER + User;
        var models = await _cache.GetValueAsync<List<UserCacheModel>>(cacheKey);
        if (models is not null)
        {
            var cacheModel = models.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
            if (cacheModel is not null)
            {
                models.Remove(cacheModel);
            }
        }

        //刷新在线用户列表
        var cacheKeyUsers = AppSettings.CACHE_APPLICATION + CACHE_ONLINE_USERS;
        var users = await _cache.GetValueAsync<List<string>>(cacheKeyUsers);
        if (models is null || models.Count == 0)
        {
            await _cache.RemoveAsync(cacheKey);
            users?.Remove(User);
        }
        else
        {
            await _cache.SetValueAsync(cacheKey, models);
        }

        if (users is null || users.Count == 0)
        {
            await _cache.RemoveAsync(cacheKeyUsers);
        }
        else
        {
            await _cache.SetValueAsync(cacheKeyUsers, users);
        }
    }
    #endregion
}